﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Reflection;
using System.Diagnostics;
using System.IO;

namespace Luna.Framework.Manifest {
	public interface IManifest: IPropertyProvider {
		string Name { get; set; }
		Type Type { get; set; }
		IManifest Parent { get; }

		bool hasProperty(string name);
		bool hasChild();
		IList<IManifest> getChildren();

		IManifestManager getManager();

		bool isEquivalent(IManifest y);
	}

	public class XmlManifestItem : IManifest {

		private XmlNode node;
		private IManifest parent;
		protected XmlManifest root;

		protected XmlManifestItem(XmlNode node, XmlManifest root, IManifest parent) {
			this.node = node;
			this.root = root;
			this.parent = parent;
		}

		public string Name {
			get {
				return node.Name;
			}
			set {
			}
		}

		public string TypeAttribute { get { return "class"; } }

		public virtual Type Type {
			get {
				string typeName = node.getAttribute("class", "").expandString();
				return typeName.getType();
			}
			set {
				node.setAttribute("class", value != null ? value.Name : "");
			}
		}

		public IManifest Parent {
			get { return parent; }
			protected set { parent = value; }
		}

		public bool hasProperty(string name) {
			return node.Attributes[name] != null;
		}

		public bool hasChild() {
			return node.HasChildNodes;
		}

		public IList<IManifest> getChildren() {
			if (!hasChild())
				return null;
			List<IManifest> children = new List<IManifest>();
			foreach (XmlNode child in node.ChildNodes) {
				if (child.Name.Equals("extensible-point")) {
					string extPoint = child.getAttribute("point");
					IManifestManager manager = getManager();
					foreach (IManifest ext in manager.getManifests(p => p.Name.Equals("extension") && p.getProperty("point", "").Equals(extPoint)))
						if (ext.hasChild()) {
							foreach (IManifest extManifest in ext.getChildren()) {
								extManifest.setPropertyValue("Parent", this, null);
								children.Add(extManifest);
							}
						}
				}
				else
					children.Add(new XmlManifestItem(child, root, this));
			}
			return children;
		}

		public virtual IManifestManager getManager() {
			return root != null ? root.getManager() : null;
		}

		public IEnumerable<string> getPropertyNames() {
			foreach (XmlAttribute a in node.Attributes)
				yield return a.Name;
		}

		public TPropType getProperty<TPropType>(object key) {
			Debug.Assert(key != null);

			string value = node.getAttribute(key.ToString());
			return value.convertTo<TPropType>();
		}

		public TPropType getProperty<TPropType>(object key, TPropType defaultValue) {
			Debug.Assert(key != null);

			string value = node.getAttribute(key.ToString());
			try {
				return value.convertTo<TPropType>();
			}
			catch (Exception) {
				return defaultValue;
			}
		}

		public void setProperty<TPropType>(object key, TPropType value) {
			Debug.Assert(key != null);

			node.setAttribute(key.ToString(), value.ToString());
		}

		public virtual bool isEquivalent(IManifest y) {
			return y is XmlManifestItem && root.isEquivalent((y as XmlManifestItem).root);
		}

		public override string ToString() {
			StringBuilder res = new StringBuilder();
			res.Append(node.Name);
			res.Append(" {");
			foreach (XmlAttribute attr in node.Attributes) {
				res.AppendFormat(" {0}: '{1}'", attr.Name, attr.Value);
			}
			res.Append("}");
			return res.ToString();
		}
	}

	public class XmlManifest : XmlManifestItem {

		private IManifestManager manager;

		internal protected XmlManifest(XmlNode node, IManifestManager manager) : base(node, null, null) {
			root = this;
			Strings = new Dictionary<string, string>();
			XmlNode stringsNode = node.SelectSingleNode("strings");
			this.manager = manager;
			if (stringsNode != null) {
				foreach(XmlNode child in stringsNode.ChildNodes)
					if (child.Name.Equals("add"))
						Strings[child.getAttribute("name")] = child.getAttribute("value");
			}
		}

		public IDictionary<string, string> Strings { get; private set; }

		public string Filename { get; set; }

		public override IManifestManager getManager() {
			return manager;
		}

		public override bool isEquivalent(IManifest y) {
			return this == y || (y is XmlManifest && Filename.Equals((y as XmlManifest).Filename));
		}

		public override string ToString() {
			return Filename;
		}
	}

	public static class IManifestExtensions {
		
		public static object applyManifest(this IManifest manifest, IAppContext context, object target = null) {
			return Framework.Manifest.applyManifest<object>(manifest, context, target);
		}

		public static object createObject(this IManifest manifest, IAppContext context) {
			return null;
		}
	}
}
